import { Config, ErrorMessage, User } from './interface';
import { FileSystemStore } from './impl/Store'
import { IncomingMessage, ServerResponse } from 'http'
import * as zlib from 'zlib'
import { join } from 'path'
import { F2EConfig, Middleware } from 'f2e-server'
import { Route, out } from 'f2e-serve'
import { readFileSync } from 'fs';
import { AUTH_KEY, Cookie, getClientIP, getURLSearchParams } from './utils';
import createActions from './actions'
import { session_map_token, getToken } from './Session'
import { IMysqlStore } from './impl/MysqlStore';
import { IMongoStore } from './impl/MongoStore'
import { logout } from './pages/logout';
import { admin_index } from './pages/admin_index';
import { login, captcha } from './pages/login';
import { IJiraMongoStore } from './impl/JiraMongoStore';
export * from './interface';

const STYLE_HOLDER = '<link rel="stylesheet" href="../node_modules/antd/dist/antd.min.less">'


const defaultCfg = {
    test: /.*/,
    
    layout_page: readFileSync(join(__dirname, '../pages/layout.htm')).toString(),
    login_page: readFileSync(join(__dirname, '../pages/login_page.htm')).toString(),
    admin_page: readFileSync(join(__dirname, '../pages/admin_page.htm')).toString(),
    style_content: '<style>' + readFileSync(join(__dirname, '../lib/antd.min.css')).toString() + '</style>',
    js_content: readFileSync(join(__dirname, '../lib/bundle.js')).toString(),

    login_error_times: 5,
    login_url: 'login',
    logout_url: 'logout',
    admin_url: 'admin',

    error_message: {
        wrong_old_pass: '旧密码输入错误',
        wrong_new_pass: '新密码格式错误: 密码长度8-16位，需要包含大小写字母和数字',
        no_match_pass: '新密码两次输入不一致',

        no_token: 'token失效，刷新后重试',
        wrong_captcha: '验证码错误',
        account_lock: 'IP锁定请稍后重试',
        wrong_pass: '用户名或密码错误',
        expires: '登录账号已过期',
        lock_ips: '登录IP不被允许',
        no_role: '无用户角色',

        ip_change: '登录IP发生变化, 之前登录账户将被下线',
    },

    with_login_search: async (req) => decodeURIComponent(req.url?.split('?')[1] || '').replace(/[^\w\-\/\\\.]+/g, ''),
    isJsonOut: (req) => {
        const headers = req.headers || {} as any
        return /application\/json/.test(headers['content-type'] || '') 
            || /XMLHttpRequest/i.test(headers['x-requested-with'] + '')
    },

    maxAge: 86400,
    avatar_size: 100 * 1024,
    setBefore: 0
} as const

const _exports = (conf: F2EConfig, config: Config): Middleware => {
    const { renderHeaders = (h => h) } = conf
    const cfg = Object.assign({}, defaultCfg, config)
    let {
        dev,
        store, test, admin_url, layout_page, get_layout_page, style_content,
        login_url, logout_url, auth_level, login_captcha, use_avatar, avatar_size,
        setBefore, onAddUser, onDeleteUser, onUpdateRole,
        onSessionChange,
        user_expires = true, user_ip_lock = true,
    } = cfg;
    const error_message = Object.assign<ErrorMessage, ErrorMessage | undefined, ErrorMessage | undefined>({}, defaultCfg.error_message, config.error_message)
    store = store || new FileSystemStore()
    const getLayoutPage = get_layout_page || (() => layout_page?.replace('$__all_config__', JSON.stringify({
        dev, login_url, logout_url, admin_url, user_expires, user_ip_lock, auth_level, login_captcha: login_captcha, use_avatar, avatar_size,
        store: {
            usePasswordChange: store?.usePasswordChange,
            apiRoot: store?.apiRoot,
            noAddUser: store?.noAddUser,
        },
    })).replace(STYLE_HOLDER, style_content || ''));
    
    const { changePassword, loginUser, token, user_list, avatar, avatarUpdate, ...otherActions } = createActions(store, cfg)
    
    if (onAddUser) {
        let addUser = otherActions.addUser
        otherActions.addUser = (req) => {
            onAddUser?.(req)
            return addUser(req)
        }
    }
    if (onDeleteUser) {
        let delUser = otherActions.delUser
        otherActions.delUser = (req) => {
            onDeleteUser?.(req)
            return delUser(req)
        }
    }
    if (onUpdateRole) {
        let updateUserRole = otherActions.updateUserRole
        otherActions.updateUserRole = (req) => {
            onUpdateRole?.(req)
            return updateUserRole(req)
        }
    }
    if (onSessionChange) {
        onSessionChange(session_map_token)
    }

    /**
     * 登录相关页面， 不验证权限
     */
    const route = new Route()
    route.on(login_url, login(cfg, store, getLayoutPage, renderHeaders) as any)
    route.on(logout_url, function (req, resp) {
        logout(cfg)(req, resp)
        return false
    })
    /**
     * 验证码支持
     */
    if (cfg.login_captcha) {
        route.on(admin_url + '/captcha', captcha(cfg.login_captcha, renderHeaders))
    }
    route.on(admin_url + '/bundle.js', (req, resp) => {
        if (dev) {
            return 'lib/bundle.js'
        } else {
            resp.writeHead(200, renderHeaders({
                'Content-Encoding': 'gzip',
                'Content-Type': 'text/html; charset=utf-8'
            }, req))
            resp.end(zlib.gzipSync(defaultCfg.js_content))
            return false
        }
    })

    /**
     * 登录用户相关API，所有登录用户可用
     */
    const route1 = new Route()
    route1.on(admin_url, admin_index(cfg, getLayoutPage, renderHeaders))
    route1.on(admin_url + '/changePassword', out.JsonOut(changePassword as any, conf))
    route1.on(admin_url + '/avatar', out.Base('png')(avatar as any, conf))
    route1.on(admin_url + '/avatarUpdate', out.JsonOut(avatarUpdate as any, conf))
    route1.on(admin_url + '/loginUser', out.JsonOut(loginUser, conf))
    route1.on(admin_url + '/token', out.JsonOut(token, conf))
    route1.on(admin_url + '/user_list', out.JsonOut(user_list, conf))

    /** apis */
    if (store.routes) {
        store.routes.forEach(([key, exec]) => route1.on(key, exec))
    }

    /**
     * 管理员用户相关API，只有管理员可以进入
     */
    const route2 = new Route()
    Object.keys(otherActions).map(k => {
        route2.on(admin_url + '/' + k, out.JsonOut(otherActions[k], { gzip: true }))
    })
    return {
        setBefore,
        beforeRoute: (pathname, req, resp) => {
            // 进入登录页面
            if (route.match(pathname)) {
                return pathname
            }
            if (test.test(pathname)) {
                const { host = '' } = req.headers
                const session_id = Cookie.parse(req.headers.cookie)[host.replace(/[^:]+/, AUTH_KEY)] || req.headers.token || getURLSearchParams(req).get('token')
                const token = getToken(session_id + '') || session_id
                const user = store?.getLoginUser(token + '')
                let error: string | undefined
                if (!user) {
                    error = error_message.wrong_pass
                } else if (user && user.expires && user.expires < Date.now()) {
                    error = error_message.expires
                } else if (user && user.lock_ips && user.lock_ips.length && !user.lock_ips.includes(getClientIP(req))) {
                    error = error_message.lock_ips
                } else if (user && !user.role) {
                    error = error_message.no_role
                }

                if (error) {
                    resp.writeHead(302, renderHeaders({
                        location: `/${login_url}?/${pathname.replace(/[^\w\/\\\.]+/g, '')}`
                    }))
                    resp.end()
                    return false
                } else if (user) {
                    Object.assign(user, { login_ip: getClientIP(req) })
                    Object.assign(req, {
                        loginUser: user
                    })
                }
            }
        },
        onRoute: (pathname: string, req: IncomingMessage, resp: ServerResponse, mem: any) => {
            const user = req['loginUser']
            if (false === route.execute(pathname, req, resp)) {
                return false
            }
            if (false === route1.execute(pathname, req, resp, mem)) {
                return false
            }
            if (user && user.role && user.role.isAdmin && false === route2.execute(pathname, req, resp, mem)) {
                return false
            }
        }
    }
}

Object.defineProperties(_exports, {
    FileSystemStore: {
        get: () => FileSystemStore
    },
    MysqlStore: {
        get: () => require('./impl/MysqlStore').MysqlStore
    },
    UniteMysqlStore:{
        get:()=>require('./impl/UniteMysqlStore').UniteMysqlStore
    },
    JiraMongoStore: {
        get: () => require('./impl/JiraMongoStore').JiraMongoStore
    },
    MongoStore: {
        get: () => require('./impl/MongoStore').MongoStore
    }
})

declare var MysqlStore: IMysqlStore
declare var MongoStore: IMongoStore
declare var JiraMongoStore: IJiraMongoStore
export { FileSystemStore, MysqlStore, MongoStore, JiraMongoStore }
module.exports = _exports